/*
 * @brief:
 * @Author: TzzzT
 * @Date: 2022-04-05 15:51:56
 * @LastEditTime: 2023-05-05 00:52:25
 * @LastEditors: TzzzT
 */
/*
 * @Description:
 * @Author: TzzzT
 * @Date: 2022-04-05 15:51:56
 * @LastEditTime: 2022-04-21 00:03:06
 * @LastEditors: TzzzT
 */

#include "nvs.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "string.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "esp_timer.h"

#include "RCS_ble_slave.h"
#include "RCS_slave.h"
#include "RCS_scr.h"
#include "RCS_protocol.h"
#include "by8301.h"

static const char *SYS_TAG = "SYS_TAG";

static const char *SHOCK_TAG = "SHOCK_TAG";
static const char *BATT_TAG = "BATT_TAG";

extern TaskHandle_t txTaskHandle;
EventGroupHandle_t led_eventGroup;

// static SemaphoreHandle_t xSystemMutex = NULL;

rcs_slave_system_t rcsSystem = {
    .device.is_connect_master = DISCONNECT_BLE,
    .device.is_net_up = false,
    .game.start = false,
    .game.player.bullet_nums = 30,
    .game.player.damage = 0,
    .game.player.hp = 100,
    .game.player.id = 0,
    .game.player.death = 0,
    .game.player.kill = 0,
    .game.player.team_score = 0,
    .game.player.room = 0,
    .game.player.team = 0,
    .game.player.living = true,
};

bool add_player_kill(player_kill_t kill)
{
    bool change = (rcsSystem.game.player.kill < kill) ? true : false;
    rcsSystem.game.player.kill = kill;
    return change;
}

// static bullet_num_t current_magazine_bullet; // 当前弹夹的子弹数。

// bullet_num_t get_current_magazine_bullet(void)
// {
//     return current_magazine_bullet;
// }

// void reload_magazine_bullet(magazine_capacity_t bullets)
// {
//     if (rcsSystem.game.player.bullet_nums > bullets)
//     {
//         current_magazine_bullet = bullets;
//         rcsSystem.game.player.bullet_nums -= bullets;
//     }
//     else
//     {
//         current_magazine_bullet = rcsSystem.game.player.bullet_nums;
//         rcsSystem.game.player.bullet_nums = 0;
//     }
// }

bool change_player_bullet_nums(bullet_num_t new_bullet_num)
{
    if (new_bullet_num < 0)
    {
        new_bullet_num = 0;
    }
    else if (new_bullet_num > 999)
    {
        new_bullet_num = 999;
    }
    bool change = (rcsSystem.game.player.bullet_nums != new_bullet_num) ? true : false;
    rcsSystem.game.player.bullet_nums = new_bullet_num;
    return change;
}

bool change_game_state(bool new_state)
{
    bool change = (rcsSystem.game.start != new_state) ? true : false;
    rcsSystem.game.start = new_state;
    return change;
}

bool change_living(bool new_state)
{
    bool change = (rcsSystem.game.player.living != new_state) ? true : false;
    rcsSystem.game.player.living = new_state;
    return change;
}
// void change_player_living(bool new_living, uint8_t* change){
//     if(rcsSystem.game.player.living != new_living){
//         rcsSystem.game.player.living = new_living;
//         change = (new_living)?1:2;
//     }else{
//         *change = 0;
//     }
//     return;
// }

// void change_game_start(bool new_state, uint8_t* change){
//     if(rcsSystem.game.start != new_state){
//         rcsSystem.game.start = new_state;
//         change = (new_state)?1:2;
//     }else{
//         *change = 0;
//     }
//     return;
// }

/////////////////////GAME////////////////////
uint8_t *make_verify_package(uint8_t *pack_len)
{
    uint8_t package_len = PACKAGE_LEN_UPL_LEN + MESSAGE_TYPE_BATTERY_UPL_LEN;
    uint8_t *package = calloc(1, package_len * sizeof(uint8_t));
    uint16_t data_head = 0;
    uint8_t data_len;

    data_len = PACKAGE_LEN_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = PACKAGE_LEN_UPL;
    package[data_head + 2] = package_len;
    data_head += data_len;

    data_len = MESSAGE_TYPE_BATTERY_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = MESSAGE_TYPE_BATTERY_UPL;
    if (rcsSystem.device.device_class == GUN_SCREEN)
    {
        package[data_head + 2] = GUN_SCREEN_VER_TYPE;
    }
    else
    {
        package[data_head + 2] = GUN_POINT_VER_TYPE;
    }
    package[data_head + 3] = rcsSystem.device.battery;
    data_head += data_len;
    *pack_len = package_len;
    return package;
}

uint8_t *make_battery_package(uint8_t *pack_len)
{
    uint8_t package_len = PACKAGE_LEN_UPL_LEN + MESSAGE_TYPE_BATTERY_UPL_LEN;
    uint8_t *package = calloc(1, package_len * sizeof(uint8_t));
    uint16_t data_head = 0;
    uint8_t data_len;

    data_len = PACKAGE_LEN_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = PACKAGE_LEN_UPL;
    package[data_head + 2] = package_len;
    data_head += data_len;

    data_len = MESSAGE_TYPE_BATTERY_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = MESSAGE_TYPE_BATTERY_UPL;
    if (rcsSystem.device.device_class == GUN_SCREEN)
    {
        package[data_head + 2] = GUN_SCREEN_BATTERY_TYPE;
    }
    else
    {
        package[data_head + 2] = GUN_POINT_BATTERY_TYPE;
    }
    package[data_head + 3] = rcsSystem.device.battery;
    data_head += data_len;
    *pack_len = package_len;
    return package;
}

uint8_t *make_disconnect_package(uint8_t *pack_len)
{
    uint8_t package_len = PACKAGE_LEN_UPL_LEN + MESSAGE_TYPE_BATTERY_UPL_LEN;
    uint8_t *package = calloc(1, package_len * sizeof(uint8_t));
    uint16_t data_head = 0;
    uint8_t data_len;

    data_len = PACKAGE_LEN_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = PACKAGE_LEN_UPL;
    package[data_head + 2] = package_len;
    data_head += data_len;

    data_len = MESSAGE_TYPE_BATTERY_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = MESSAGE_TYPE_BATTERY_UPL;
    if (rcsSystem.device.device_class == GUN_SCREEN)
    {
        package[data_head + 2] = GUN_SCREEN_CLOSE_CONNECT_TYPE;
    }
    else
    {
        package[data_head + 2] = GUN_POINT_CLOSE_CONNECT_TYPE;
    }
    package[data_head + 3] = rcsSystem.device.battery;
    data_head += data_len;

    *pack_len = package_len;
    return package;
}

uint8_t *make_command_package(uint8_t command, uint8_t *pack_len)
{
    uint8_t package_len = PACKAGE_LEN_UPL_LEN + MESSAGE_TYPE_BATTERY_UPL_LEN + COMMAND_UPL_LEN;
    uint8_t *package = calloc(1, package_len * sizeof(uint8_t));
    uint16_t data_head = 0;
    uint8_t data_len;

    data_len = PACKAGE_LEN_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = PACKAGE_LEN_UPL;
    package[data_head + 2] = package_len;
    data_head += data_len;

    data_len = MESSAGE_TYPE_BATTERY_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = MESSAGE_TYPE_BATTERY_UPL;
    if (rcsSystem.device.device_class == GUN_SCREEN)
    {
        package[data_head + 2] = GUN_SCREEN_COMMAND_TYPE;
    }
    else
    {
        package[data_head + 2] = GUN_POINT_COMMAND_TYPE;
    }
    package[data_head + 3] = rcsSystem.device.battery;
    data_head += data_len;

    data_len = COMMAND_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = COMMAND_UPL;
    package[data_head + 2] = command;
    data_head += data_len;

    *pack_len = package_len;
    return package;
}

uint8_t *make_ble_fire_success_package(uint8_t *pack_len)
{
    uint8_t package_len = PACKAGE_LEN_UPL_LEN + MESSAGE_TYPE_BATTERY_UPL_LEN + REMAIN_BULLET_NUM_UPL_LEN;
    uint8_t *package = calloc(1, package_len * sizeof(uint8_t));
    uint16_t data_head = 0;
    uint8_t data_len;

    data_len = PACKAGE_LEN_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = PACKAGE_LEN_UPL;
    package[data_head + 2] = package_len;
    data_head += data_len;

    data_len = MESSAGE_TYPE_BATTERY_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = MESSAGE_TYPE_BATTERY_UPL;
    if (rcsSystem.device.device_class == GUN_SCREEN)
    {
        package[data_head + 2] = GUN_SCREEN_COMMAND_TYPE;
    }
    else
    {
        package[data_head + 2] = GUN_POINT_FIRE_TYPE;
    }
    package[data_head + 3] = rcsSystem.device.battery;
    data_head += data_len;

    data_len = REMAIN_BULLET_NUM_UPL_LEN;
    package[data_head] = data_len;
    package[data_head + 1] = REMAIN_BULLET_NUM_UPL;
    package[data_head + 2] = (rcsSystem.game.player.bullet_nums & 0xFF00) >> 8;
    package[data_head + 3] = (rcsSystem.game.player.bullet_nums & 0x00FF);
    data_head += data_len;

    *pack_len = package_len;
    return package;
}

static const char *GAME_TAG = "GAME_TAG";
extern display_t rcsDisplay;
extern TaskHandle_t nfcTaskHandle;

void analyse_master_package(uint8_t *buf, uint8_t buf_len)
{
    bool be_hitted_flag = false;
    // ESP_LOGE(SYS_TAG, "start analyse master data");
    uint16_t struct_data_head = 0;
    uint16_t struct_data_len = buf[struct_data_head];
    notify_data_type_t struct_data_type = buf[struct_data_head + 1];
    if (struct_data_type != PACKAGE_LEN_NOTIFY)
    { // 校验一下
        ESP_LOGE(SYS_TAG, "master data is invalid.");
        return;
    }
    uint8_t package_len = buf[struct_data_head + 2];
    if (package_len != buf_len)
    {
        ESP_LOGE(SYS_TAG, "master datalen error. physical: %d, target: %d.", buf_len, package_len);
        return;
    }
    struct_data_head += struct_data_len;
    // 开始检测具体消息
    while (struct_data_head < package_len)
    {
        struct_data_len = buf[struct_data_head];
        struct_data_type = buf[struct_data_head + 1];
        switch (struct_data_type)
        {
        case PLAYER_ID_NOTIFY:
        {
            rcsSystem.game.player.id = 0;
            rcsSystem.game.player.id |= buf[struct_data_head + 2] << 16;
            rcsSystem.game.player.id |= buf[struct_data_head + 3] << 8;
            rcsSystem.game.player.id |= buf[struct_data_head + 4];
            ESP_LOGE(GAME_TAG, "new player id: %" PRIu32 ".", rcsSystem.game.player.id);

            // 初始化的时候会发id。把这个认为是发送了初始化消息的标志。
            rcsSystem.device.is_connect_master = CONNECTED_BLE;
            if (eTaskGetState(nfcTaskHandle) == eSuspended)
            {
                ESP_LOGE(GAME_TAG, "vTaskResume( nfcTaskHandle )");
                vTaskResume(nfcTaskHandle);
            }
            break;
        }
        case LIVING_NOTIFY:
        {
            if (change_living(buf[struct_data_head + 2]))
            {
                ESP_LOGE(GAME_TAG, "new living state: %d.", rcsSystem.game.player.living);
                if (eTaskGetState(txTaskHandle) == eSuspended)
                {
                    ESP_LOGE(GAME_TAG, "vTaskResume( txTaskHandle )");
                    vTaskResume(txTaskHandle);
                }
                if (rcsSystem.game.player.living)
                {
                    ESP_LOGE(GAME_TAG, "need play reborn audio.");
                    play_new_song(RRBORN_SOUND);
                }
                else
                {
                    ESP_LOGE(GAME_TAG, "need play dead audio.");
                    play_new_song(DEAD_SOUND);
                }
            }
            break;
        }
        case GAME_START_NOTIFY:
        {
            if (change_game_state(buf[struct_data_head + 2]))
            {
                ESP_LOGE(GAME_TAG, "new game.start: %d.", rcsSystem.game.start);
                if (eTaskGetState(txTaskHandle) == eSuspended)
                {
                    ESP_LOGE(GAME_TAG, "vTaskResume( txTaskHandle )");
                    vTaskResume(txTaskHandle);
                }
                if (rcsSystem.game.start)
                {
                    ESP_LOGE(GAME_TAG, "need play startgame audio.");
                    play_new_song(START_GAME_SOUND);
                }
                else
                {
                    ESP_LOGE(GAME_TAG, "need play endgame audio.");
                    play_new_song(END_GAME_SOUND);
                }
            }
            break;
        }
        case TEAM_NOTIFY:
        {
            rcsSystem.game.player.team = buf[struct_data_head + 2];
            ESP_LOGE(GAME_TAG, "new team: %d.", rcsSystem.game.player.team);
            break;
        }
        case ROOM_NOTIFY:
        {
            rcsSystem.game.player.room = buf[struct_data_head + 2];
            ESP_LOGE(GAME_TAG, "new room: %d.", rcsSystem.game.player.room);
            break;
        }
        case HP_NOTIFY:
        {
            rcsSystem.game.player.hp = 0;
            rcsSystem.game.player.hp |= buf[struct_data_head + 2] << 8;
            rcsSystem.game.player.hp |= buf[struct_data_head + 3];
            ESP_LOGE(GAME_TAG, "new hp: %d.", rcsSystem.game.player.hp);
            break;
        }
        case DAMAGE_NOTIFY:
        {
            rcsSystem.game.player.damage = 0;
            rcsSystem.game.player.damage |= buf[struct_data_head + 2] << 8;
            rcsSystem.game.player.damage |= buf[struct_data_head + 3];
            ESP_LOGE(GAME_TAG, "new damage: %d.", rcsSystem.game.player.damage);
            break;
        }
        case BULLET_NUM_NOTIFY:
        {
            rcsSystem.game.player.bullet_nums = 0;
            rcsSystem.game.player.bullet_nums |= buf[struct_data_head + 2] << 8;
            rcsSystem.game.player.bullet_nums |= buf[struct_data_head + 3];
            ESP_LOGE(GAME_TAG, "new bullet_nums: %d.", rcsSystem.game.player.bullet_nums);
            break;
        }
        case CONN_CLI_NUM_NOTIFY:
        {
            if (rcsSystem.device.is_hold_scr)
            {
                rcsDisplay.conn_dev_num = buf[struct_data_head + 2];
                ESP_LOGE(GAME_TAG, "new connect nums: %d.", rcsDisplay.conn_dev_num);
            }
            break;
        }
        case LOW_WARNING_NOTIFY:
        {
            if (rcsSystem.device.is_hold_scr)
            {
                rcsDisplay.low_batt = buf[struct_data_head + 2];
                ESP_LOGE(GAME_TAG, "new low_batt warning: %d.", rcsDisplay.low_batt);
            }
            break;
        }
        case AUDIO_SN_NOTIFY:
        {
            ESP_LOGE(GAME_TAG, "need play audio sn: %d.", buf[struct_data_head + 2]);
            play_new_song(buf[struct_data_head + 2]);
            break;
        }
        case DEATH_NOTIFY:
        {
            rcsSystem.game.player.death = buf[struct_data_head + 2];
            ESP_LOGE(GAME_TAG, "new death: %d.", rcsSystem.game.player.death);
            break;
        }
        case KILL_NOTIFY:
        {
            if (add_player_kill(buf[struct_data_head + 2]))
            {
                ESP_LOGE(GAME_TAG, "new kill: %d.", rcsSystem.game.player.kill);
                if (rcsSystem.game.player.living && rcsSystem.game.start)
                {
                    play_new_song(KILL_SOUND);
                }
            }
            break;
        }
        case TEAM_SCORE_NOTIFY:
        {
            rcsSystem.game.player.team_score = 0;
            rcsSystem.game.player.team_score |= buf[struct_data_head + 2] << 8;
            rcsSystem.game.player.team_score |= buf[struct_data_head + 3];
            ESP_LOGE(GAME_TAG, "new team_score: %d.", rcsSystem.game.player.team_score);
            break;
        }
        case CLOSE_CONNECT_NOTIFY:
        {
            rcsSystem.device.wait_new_master = true;
            ESP_LOGE(GAME_TAG, "disconnect from master. set wait_new_master:%d", rcsSystem.device.wait_new_master);
            break;
        }
        case POWER_OFF_NOTIFY:
        {
            ESP_LOGE(GAME_TAG, "need power off.");
            power_off();
            break;
        }
        case BE_HITTED_NOTIFY:
        {
            ESP_LOGE(GAME_TAG, "need play audio.");
            be_hitted_flag = true;
            break;
        }
        case SCRHINT_NOTIFY:
        {
            if (rcsSystem.device.is_hold_scr)
            {
                ESP_LOGE(GAME_TAG, "refresh screen content");
                esp_log_buffer_char("", &buf[struct_data_head + 2], 7);
                memcpy(rcsDisplay.hint, &buf[struct_data_head + 2], 7);
            }
            break;
        }
        case SCRBATT_NOTIFY:
        {
            if (rcsSystem.device.is_hold_scr)
            {
                // ESP_LOGE(GAME_TAG, "refresh all batt");
                int i = 0;
                int min_batt = 100;
                for (i = 0; i < 4; ++i)
                {
                    rcsDisplay.batt[i] = (int)(buf[struct_data_head + 2 + i]);
                    if (rcsDisplay.batt[i] >= 0 && rcsDisplay.batt[i] < min_batt)
                    {
                        min_batt = rcsDisplay.batt[i];
                    }
                }
                rcsDisplay.batt[4] = min_batt;
            }
            break;
        }
        case NET_STATE_NOTIFY:
        {
            rcsSystem.device.is_net_up = buf[struct_data_head + 2];
            break;
        }
        default:
            break;
        }
        struct_data_head += struct_data_len;
    }
    if (be_hitted_flag && rcsSystem.game.player.living)
    {
        play_new_song(BE_HITTED_SOUND);
    }
    // ESP_LOGE(GAME_TAG, "analyse end.");
}

void game_event_init(void)
{
    led_eventGroup = xEventGroupCreate();
}

/**
 * @brief: 设置新的事件到rtos事件组里，在设置前线清空所有bit，保证只有一个bit为1
 * @param  {*}
 * @return {*}
 * @Author: TzzzT
 * @Date: 2022-04-20 00:41:36
 * @LastEditTime: Do not edit
 * @LastEditors: Shadow
 * @param {rcs_event_t} new_event
 */
void set_game_event(rcs_event_t new_event)
{
    xEventGroupClearBits(led_eventGroup, CONNECT_SUCCESS_EVENT_BIT |
                                             REMOTE_COMM_EVENT_BIT |
                                             BE_HITTED_EVENT_BIT |
                                             HIT_SUCCESS_EVENT_BIT);
    switch (new_event)
    {
    case CONNECT_SUCCESS:
        xEventGroupSetBits(led_eventGroup, CONNECT_SUCCESS_EVENT_BIT);
        break;
    case REMOTE_COMM_EVENT:
        xEventGroupSetBits(led_eventGroup, REMOTE_COMM_EVENT_BIT);
        break;
    case BE_HITTED:
        xEventGroupSetBits(led_eventGroup, BE_HITTED_EVENT_BIT);
        break;
    case HIT_SUCCESS:
        xEventGroupSetBits(led_eventGroup, HIT_SUCCESS_EVENT_BIT);
        break;
    case NO_EVENT:
        // 啥也不干
        break;
    default:
        break;
    }
}

/**
 * @brief: 获取事件组状态
 * @param  {*}
 * @return {*}
 * @Author: TzzzT
 * @Date: 2022-04-20 00:40:59
 * @LastEditTime: Do not edit
 * @LastEditors: Shadow
 * @param {rcs_event_t*} new_event
 */
void get_game_event_state(rcs_event_t *new_event)
{
    EventBits_t bits;
    bits = xEventGroupGetBits(led_eventGroup);
    if (bits == 0)
    {
        *new_event = (rcs_event_t)NO_EVENT;
    }
    else if (bits == CONNECT_SUCCESS_EVENT_BIT)
    {
        *new_event = (rcs_event_t)CONNECT_SUCCESS;
    }
    else if (bits == (rcs_event_t)REMOTE_COMM_EVENT_BIT)
    {
        *new_event = REMOTE_COMM_EVENT;
    }
    else if (bits == BE_HITTED_EVENT_BIT)
    {
        *new_event = (rcs_event_t)BE_HITTED;
    }
    else if (bits == HIT_SUCCESS_EVENT_BIT)
    {
        *new_event = (rcs_event_t)HIT_SUCCESS;
    }
}

uint8_t noevent[] = "no event";
uint8_t connect[] = "connect success";
uint8_t remote[] = "receive yaokong";
uint8_t behit[] = "be hitted";
uint8_t hitsuccess[] = "hit success";

uint8_t *event_name(rcs_event_t event)
{
    uint8_t *name;
    switch (event)
    {
    case NO_EVENT:
        name = noevent;
        break;
    case CONNECT_SUCCESS:
        name = connect;
        break;
    case REMOTE_COMM_EVENT:
        name = remote;
        break;
    case BE_HITTED:
        name = behit;
        break;
    case HIT_SUCCESS:
        name = hitsuccess;
        break;
    default:
        name = NULL;
        break;
    }
    return name;
}

//////////////////////////SHOCK//////////////////////////

void shock_init(void)
{
    ESP_LOGI(SHOCK_TAG, "shock_init");
    gpio_reset_pin(SHOCK_OUTPUT_PIN);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(SHOCK_OUTPUT_PIN, GPIO_MODE_OUTPUT);
}
void set_shock(bool state)
{
    /* Set the GPIO level according to the state (LOW or HIGH)*/
    gpio_set_level(SHOCK_OUTPUT_PIN, state);
}

//////////////////////////POWER//////////////////////////

/**
 * @brief: 在按钮初始化以后调用此函数，先是手动保持按钮按下来供电，保持2s，同时正在执行delay_ms。
 *          2s，以后，在此程序种将引脚拉高，保证供电，可以松开按钮
 * @param  {*}
 * @return {*}
 * @Author: TzzzT
 * @Date: 2022-05-11 19:25:21
 * @LastEditTime: Do not edit
 * @LastEditors: Shadow
 */
void power_init(void)
{
    gpio_reset_pin(POWER_PIN);
    gpio_set_direction(POWER_PIN, GPIO_MODE_INPUT_OUTPUT);
    gpio_set_level(POWER_PIN, true);
}
void power_off(void)
{
    gpio_set_level(POWER_PIN, false);
}
